package com.swak.mmap;

import static java.lang.invoke.MethodHandles.lookup;
import static java.lang.invoke.MethodType.methodType;

import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * BufferCleaner.
 * 
 * @author 618lf
 */
public interface BufferCleaner {

	Logger LOGGER = LoggerFactory.getLogger(BufferCleaner.class);

	/**
	 * 释放内存
	 * 
	 * @param resourceDescription
	 * @param b
	 * @throws IOException
	 */
	void freeBuffer(String resourceDescription, ByteBuffer b) throws IOException;

	private static BufferCleaner newBufferCleaner(final MethodHandle unmapper) {
		assert Objects.equals(methodType(void.class, ByteBuffer.class), unmapper.type());
		return (String resourceDescription, ByteBuffer buffer) -> {
			if (!buffer.isDirect()) {
				throw new IllegalArgumentException("unmapping only works with direct buffers");
			}
			try {
				unmapper.invokeExact(buffer);
			} catch (Throwable t) {
				throw new IOException("Unable to unmap the mapped buffer: " + resourceDescription, t);
			}
		};
	}

	public static BufferCleaner newBufferCleaner() {
		final Lookup lookup = lookup();
		try {
			// *** sun.misc.Unsafe unmapping (Java 9+) ***
			final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
			// first check if Unsafe has the right method, otherwise we can give up
			// without doing any security critical stuff:
			final MethodHandle unmapper = lookup.findVirtual(unsafeClass, "invokeCleaner",
					methodType(void.class, ByteBuffer.class));
			// fetch the unsafe instance and bind it to the virtual MH:
			final Field f = unsafeClass.getDeclaredField("theUnsafe");
			f.setAccessible(true);
			final Object theUnsafe = f.get(null);
			return newBufferCleaner(unmapper.bindTo(theUnsafe));
		} catch (SecurityException se) {
			LOGGER.debug(
					"Unmapping is not supported, because not all required permissions are given to the Lucene JAR file: "
							+ se
							+ " [Please grant at least the following permissions: RuntimePermission(\"accessClassInPackage.sun.misc\") "
							+ " and ReflectPermission(\"suppressAccessChecks\")]");
			return null;
		} catch (ReflectiveOperationException | RuntimeException e) {
			final Module module = BufferCleaner.class.getModule();
			final ModuleLayer layer = module.getLayer();
			// classpath / unnamed module has no layer, so we need to check:
			if (layer != null && layer.findModule("jdk.unsupported").map(module::canRead).orElse(false) == false) {
				LOGGER.debug("Unmapping is not supported, because Lucene cannot read 'jdk.unsupported' module "
						+ "[please add 'jdk.unsupported' to modular application either by command line or its module descriptor]");
			} else {
				LOGGER.debug(
						"Unmapping is not supported on this platform, because internal Java APIs are not compatible with this Lucene version: "
								+ e);
			}
			return null;
		}
	}
}
